/*
 * Copyright (c) 2010, artur
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * kiddie-os
 * Created on: 11.05.2010
 *     Author: artur
 *
 */

#ifdef __KERNEL__

#	include <kiddie/kiddie.h>
#	include <Console.h>
#	include <hw/Platform.h>
#	include <sys/sched/Scheduler.h>
#	include <virt/Llvm.h>

extern kernel::Console cout;
extern kernel::Platform hwplatform;
extern kernel::sys::sched::Scheduler *scheduler;

#else
#include <virt/Llvm.h>
#include <iostream>
using std::cout;
#endif

namespace kernel
{
  namespace virt
  {
    namespace llvm
    {

      Llvm::Llvm()
      {
        this->bpc = 0;
        this->opw = 2;
        this->sp = stack;
      }

      Llvm::~Llvm()
      {
      }

      void
      Llvm::stack_push(nvm_stack_t val)
      {
        *(++this->sp) = val;
      }

      nvm_stack_t
      Llvm::stack_pop()
      {
        return *(this->sp--);
      }

      /**
       * @return block len in bits
       */
      bool
      Llvm::__read_block()
      {
        bool err = false;
        int res;
        unsigned int bitlen = 0;
        unsigned int opcode;
        unsigned int bllen;
        unsigned int len;
        unsigned int cmdwidth;
        unsigned int numabbrevops;

        // ::cout << "-> BPC=" << (int) this->bpc << "\n";

        while (1)
          {
            opcode = readbits(this->opw);

            switch (opcode)
              {
            case bitc::END_BLOCK:
              ::cout << "	</END_BLOCK>\n";
              bpc_trunc(bitc::BlockSizeWidth);
              ::cout << (int) this->bpc << ":";
              // restore opw
              this->opw = stack_pop();
              break;

            case bitc::ENTER_SUBBLOCK:
              ::cout << "ENTER_SUBBLOCK\n";
              /* read block header */
              opcode = __read_vbr(bitc::BlockIDWidth);
              // save prev opw
              this->stack_push(this->opw);
              this->opw = __read_vbr(bitc::CodeLenWidth);
              bpc_trunc(bitc::BlockSizeWidth);
              bllen = readbits(bitc::BlockSizeWidth);
              // bpc_trunc(bitc::BlockSizeWidth);
              ::cout << (int) this->bpc << ":";
              ::cout << "	BLOCK ID=" << (int) opcode << "\n";
              ::cout << "	CodeSize Len=" << (int) this->opw << "\n";
              ::cout << "	NumWords Len=" << (int) bllen << "\n";

              switch (opcode)
                {
              // top level block
              case bitc::MODULE_BLOCK_ID:
                ::cout << "	<MODULE_BLOCK>\n";
                break;

                // this is block info
              case bitc::BLOCKINFO_BLOCK_ID:
                ::cout << "	<BLOCKINFO_BLOCK>\n";
                break;

              case bitc::PARAMATTR_BLOCK_ID:
                ::cout << "	<PARAMATTR_BLOCK>\n";
                break;

              case bitc::TYPE_BLOCK_ID:
                ::cout << "	<TYPE_BLOCK>\n";
                // skip block
                this->bpc += (bllen) * 32;
                this->opw = stack_pop();
                break;

              case bitc::TYPE_SYMTAB_BLOCK_ID:
                ::cout << "	<TYPE_SYMTAB_BLOCK>\n";
                // skip block
                this->bpc += (bllen) * 32;
                this->opw = stack_pop();
                break;

              case bitc::VALUE_SYMTAB_BLOCK_ID:
                ::cout << "	<VALUE_SYMTAB_BLOCK>\n";
                // skip block
                this->bpc += (bllen) * 32;
                this->opw = stack_pop();
                break;

              case bitc::CONSTANTS_BLOCK_ID:
                ::cout << "	<CONSTANTS_BLOCK>\n";
                break;

              case bitc::METADATA_BLOCK_ID:
                ::cout << "	<METADATA_BLOCK>\n";
                // skip block
                this->bpc += (bllen) * 32;
                this->opw = stack_pop();
                break;

              case bitc::FUNCTION_BLOCK_ID:
                ::cout << "	<FUNCTION_BLOCK>\n";
                break;

              default:
                return false;
                break;
                }

              break;

            case bitc::DEFINE_ABBREV:

              ::cout << (int) this->bpc << ":";
              ::cout << "	-- DEFINE_ABBREV () --\n";

              numabbrevops = __read_vbr(5);

              for (int i = 0; i < numabbrevops; i++)
                {
                  // ::cout << this->bpc << ":";

                  if (readbits(1)) // is literal
                    {
                      opcode = __read_vbr(8);
                      ::cout << "			literal" << i << "(" << (int) opcode
                          << ")\n";
                      continue;
                    }
                  else
                    {
                      len = readbits(3);
                      ::cout << "			op" << (int) i << "(" << (int) len;
                      switch (len)
                        {
                      case bitc::Fixed:
                        opcode = __read_vbr(5);
                        ::cout << "_fix" << (int) opcode;
                        break;
                      case bitc::VBR:
                        opcode = __read_vbr(5);
                        ::cout << "_vbr" << (int) opcode;
                        break;

                      case bitc::Blob:
                      case bitc::Array:
                      case bitc::Char6:
                        ::cout << "_array" << (int) opcode;

                      default:
                        break;
                        };
                      ::cout << ");\n";
                    }
                }

              break;

            case bitc::UNABBREV_RECORD:
              ::cout << (int) this->bpc << ":";
              ::cout << "	-- UNABBREV_RECORD -- \n";
              opcode = __read_vbr(6);

              switch (opcode)
                {
              case bitc::TYPE_CODE_INTEGER:
                ::cout << "		INTEGER:";
                break;
              case bitc::TYPE_CODE_NUMENTRY:
                ::cout << "		NUMENTRY:";
                break;

              case bitc::FUNC_CODE_INST_CALL:
                ::cout << "		INST_CALL ";
                break;
              case bitc::FUNC_CODE_INST_ALLOCA:
                ::cout << "		INST_ALLOCA ";
                break;
              case bitc::FUNC_CODE_INST_STORE2:
                ::cout << "		INST_STORE2 ";
                break;
              default:
                ::cout << "		type " << (int) opcode << ": ";
                break;
                }
              ;

              opcode = __read_vbr(6);

              for (int i = 0; i < opcode; i++)
                {
                  len = __read_vbr(6);
                  ::cout << " op" << (int) i << "=" << (int) len << ",";
                }
              // ::cout << "		regs.bpc = " << this->bpc << "\n";
              ::cout << "\n";
              break;

            default:
#if 0
              record = (*AbbrevDef)[opcode];
              ::cout << "		abbrev" << (int) opcode << ":";
              //std::vector<Op>::iterator opi;
              int i;
              for (i = 0; i < record->op.size(); i++)
                {
                  ::cout << " op" << i << "=";
                  Op p = record->op[i];
                  Op q;
                  if (p.type)
                    {
                      ::cout << (int) p.opcode;
                    }
                  else
                    {
                      switch (p.opcode)
                        {
                          char c;
                          case bitc::Fixed:
                          opcode = readbits(p.width);
                          ::cout << (int) opcode;
                          break;
                          case bitc::VBR:
                          opcode = __read_vbr(p.width);
                          ::cout << (int) opcode;
                          break;

                          case bitc::Blob:
                          case bitc::Array:
                          opcode = __read_vbr(6);
                          ::cout << "Array[" << (int) opcode
                          << "]=";
                          q = record->op[++i];
                          if (q.opcode == bitc::Char6)
                          ::cout << '\"';
                          for (int k = 0; k < opcode; k++)
                            {
                              if (q.opcode == bitc::VBR)
                                {
                                  ::cout << (int) __read_vbr(
                                      q.width) << ",";
                                }
                              else if (q.opcode == bitc::Char6)
                                {
                                  c = readbits(6);
                                  if (c == 62U)
                                  c = '.';
                                  else if (c == 63U)
                                  c = '_';
                                  else
                                  c = (c < 26) ? c + 'a' : (c
                                      < 52) ? c - 26
                                  + 'A' : c - 52
                                  + '0';

                                  ::cout << (char) c;
                                }
                              else
                              ::cout << (char) readbits(
                                  q.width); // << ",";
                            }
                          if (q.opcode == bitc::Char6)
                          ::cout << '\"';
                          break;

                          case bitc::Char6:
                          c = readbits(6);
                          if (c == 62U)
                          c = '.';
                          else if (c == 63U)
                          c = '_';
                          else
                          c = (c < 26) ? c + 'a'
                          : (c < 52) ? c - 26 + 'A'
                          : c - 52 + '0';
                          ::cout << (char) c;
                          default:
                          break;
                        };
                    }
                }
              ::cout << "\n";

#else
              ::cout << "Uknown record\n";
              return false;
#endif
              break;
              };

            /* restore machine state */
            //					::cout << "<- BPC=" << (int) this->bpc << "\n";
          }

        return err;
      }

      /**
       * Loads llvm module
       */
      int
      Llvm::parseBitCode()
      {
        register unsigned int r0;
        bool err;
        unsigned int opcode;
        int res = 0;

        ::cout << "Loading llvm code...\n";

        // search for llvm 'magic' number in bitstream
        res = search4(0xdec04342, this->bpc, 32);
        ::cout << "Offset = " << (int) res << "\n";

        if (res < 0)
          ::cout << "ERROR!! Invalid bitcode format!!\n";
        else
          ::cout << "OK! Bitcode format!!\n";

        this->bpc += (res + 32);
        this->opw = 2;

        // read top level block
        __read_block();

        this->bpc += this->r0; // r0 holds bit count of the block

        return 0;
      }

      /**
       * \func unsigned int kernel::virt::Llvm::read32blk(unsigned int boff)
       */
      unsigned int
      Llvm::read32blk(unsigned int off)
      {
        unsigned int r0;
        unsigned int r1;

        r1 = off & ~(sizeof(unsigned int) - 1);
#ifdef __KERNEL__

#else
        ::lseek(fd, r1, SEEK_SET);
        ::read(fd, &r0, sizeof(unsigned int));
#endif
        return r0;
      }

      /**
       *
       */
      unsigned int
      Llvm::read32(unsigned int off)
      {
        unsigned int r0;
        unsigned int r1;
        unsigned int r2;
        unsigned int r3;

        /* bit offset */
        r2 = (off % sizeof(unsigned int)) * 8;
        /* int align */
        r3 = off & ~(sizeof(unsigned int) - 1);
        /* first 32bit block */
        r0 = read32blk(r3);

        if (r2)
          {
            r1 = read32blk(r3 + sizeof(unsigned int));
            r0 >>= r2;
            r1 <<= (sizeof(unsigned int) * 8 - r2);
            r0 |= r1;
          }

        return r0;
      }

      /**
       * \brief
       */
      unsigned int
      Llvm::readbits(unsigned int bits, unsigned int boff)
      {
        unsigned int r0;
        unsigned int r1;
        unsigned int r2;
        unsigned int r3;

        /* bit offset */
        r2 = boff % (sizeof(unsigned int) * 8);
        /* int align */
        r3 = (boff / 8) & ~(sizeof(unsigned int) - 1);
        /* first 32bit block */
        r0 = read32blk(r3);

        if (r2)
          {
            r1 = read32blk(r3 + sizeof(unsigned int));
            r0 >>= r2;
            r1 <<= (sizeof(unsigned int) * 8 - r2);
            r0 |= r1;
          }

        r0 &= (bits < 32) ? ((1 << bits) - 1) : -1UL;
        // ::printf("readbits(%u, %u) = 0x%x\n", bits, boff, r0);
        return r0;
      }

      /**
       * @brief
       */
      unsigned int
      Llvm::readbits(unsigned int bits)
      {
        unsigned int r0;

        r0 = readbits(bits, this->bpc);

        this->bpc += bits;

        // ::printf("readbits(%u) = 0x%x\n", bits,r0);
        return r0;
      }

      /**
       * @brief
       */
      int
      Llvm::search4(unsigned int marker, unsigned int boff, int bsiz)
      {
        // search depth
        unsigned int r0;
        unsigned int r1;
        unsigned int r2;
        unsigned int r3;
        int depth = 128 * 8; // predefined search depth

        r3 = boff;
        r0 = (bsiz < 32) ? ((1 << bsiz) - 1) : -1;
        r1 = ~r0;

        do
          {
            if (depth-- <= 0)
              goto search_err;
            r2 = readbits(bsiz, r3++);
            r1 = (r2 - marker) & r0;
          }
        while (r1);

        return (--r3) - boff;

        search_err: ::cout << "ERROR\n";
        return -1;
      }

      /**
       *
       */
      unsigned int
      Llvm::__read_vbr(unsigned bits)
      {
        unsigned int p = readbits(bits);
        if ((p & (1U << (bits - 1))) == 0)
          return p;

        unsigned int ret = 0;
        unsigned next_bit = 0;
        while (1)
          {
            ret |= (p & ((1U << (bits - 1)) - 1)) << next_bit;

            if ((p & (1U << (bits - 1))) == 0)
              return ret;

            next_bit += bits - 1;
            p = readbits(bits);
          }
      }

      bool
      Llvm::bpc_trunc(unsigned int bits)
      {
        if (this->bpc % bits)
          this->bpc += (bits - (this->bpc % bits));
      }

    }
  }
}

#ifndef __KERNEL__

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
//#include <stdlib>

/**
 * to compile run
 *  gcc -o nanollvm Llvm.cpp ../../util/string.cpp ../Object.cpp -I../../../include/ -lstdc++
 *
 */
int
main(int argc, char **argv)
{
  struct stat f_stat;
  char *buf;
  int fd, len;

  kernel::virt::llvm::Llvm *ll = new kernel::virt::llvm::Llvm();
  ll->fd = open(argv[1], O_RDWR);
  ll->parseBitCode();
}

#endif

